/*
 * FTNANDC024 driver developed by Mychaela Falconia at CarrierComm, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <linux/io.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/mtd/mtd.h>
#include <linux/wait.h>
#include <linux/errno.h>

#include "ftnandc024_priv.h"
#include "ftnandc024_regs.h"

static int erase_one_block(struct mtd_info *mtd, uint32_t row_addr)
{
	struct ftnandc024_data *priv = to_ftnandc024_data(mtd);
	uint32_t rowaddr_endblock = row_addr + priv->pages_per_block - 1;
	uint32_t command;

	/* invalidate the cache state if the cached page is erased */
	if (priv->page_in_buffer >= row_addr &&
	    priv->page_in_buffer <= rowaddr_endblock)
		priv->page_in_buffer = -1;

	/* issue block erase command */
	writel(row_addr, priv->io_base + CMDQUEUE1(0));
	writel(CMD_COUNT(1), priv->io_base + CMDQUEUE3(0));
	if (priv->smallpage_cmds)
		command = CMD_INDEX(SMALL_FIXFLOW_ERASE);
	else
		command = CMD_INDEX(LARGE_FIXFLOW_ERASE);
	return ftnandc024_execute_command(priv, command | CMD_SCALE_BY_BLOCK);
}

static int erase_under_mutex(struct mtd_info *mtd, struct erase_info *instr)
{
	struct ftnandc024_data *priv = to_ftnandc024_data(mtd);
	uint32_t row_addr, nblocks;
	int rc;

	if (unlikely(priv->reset_needed)) {
		rc = ftnandc024_reset_nand_chip(priv);
		if (rc) {
			instr->state = MTD_ERASE_FAILED;
			instr->fail_addr = MTD_FAIL_ADDR_UNKNOWN;
			return rc;
		}
	}

	row_addr = instr->addr >> mtd->writesize_shift;
	nblocks = instr->len >> mtd->erasesize_shift;

	instr->state = MTD_ERASING;
	for (; nblocks; nblocks--) {
		rc = erase_one_block(mtd, row_addr);
		if (unlikely(rc)) {
			instr->state = MTD_ERASE_FAILED;
			instr->fail_addr =
				((loff_t)row_addr << mtd->writesize_shift);
			return rc;
		}
		row_addr += priv->pages_per_block;
	}
	instr->state = MTD_ERASE_DONE;
	return 0;
}

int ftnandc024_mtd_erase_method(struct mtd_info *mtd, struct erase_info *instr)
{
	struct ftnandc024_data *priv = to_ftnandc024_data(mtd);
	int ret;

	/* check offset and length for block alignment */
	if (instr->addr & mtd->erasesize_mask)
		return -EINVAL;
	if (instr->len & mtd->erasesize_mask)
		return -EINVAL;
	/* don't allow going past the end of the chip */
	if (instr->addr + instr->len > mtd->size)
		return -EINVAL;

	mutex_lock(&priv->mutex);
	erase_under_mutex(mtd, instr);
	mutex_unlock(&priv->mutex);

	ret = instr->state == MTD_ERASE_DONE ? 0 : -EIO;
	/* Do call back function */
	if (!ret)
		mtd_erase_callback(instr);
	/* Return more or less happy */
	return ret;
}
